package jeconbond.automata.behaviour.antstrat;

import java.util.*;

import jeconbond.automata.INode;
import jeconbond.automata.IOutcome;
import jeconbond.automata.behaviour.IWalkStrategy;
import jeconbond.economic.production.IResourceNecessityInformer;
import jeconbond.economic.resources.ResourceUtils;
import jeconbond.economic.resources.ResourceStorrage;
import jeconbond.economic.resources.ResourceBasket;
import jeconbond.economic.agent.IEconomicAgent;
import jeconbond.economic.systems.market.IMarketResourceRedestributionSystem;
import laboratoryQ.computation.IRandom;

public class FeramonStorrage implements IFeramonStorrage, IWalkStrategy, IResourceNecessityInformer {
	private Map<IOutcome, FeramonNote> outcome2note = new HashMap<IOutcome, FeramonNote>();
	private int feramonHistoryLength;
	private INode root;
	private IRandom random;
	private boolean useBranchesEuristic;
	private ResourceStorrage inputRS;
	private ResourceStorrage outputRS;

	@Override
	public IWalkStrategy getClientWalkStrategy() {
		return this;
	}

	@Override
	public IResourceNecessityInformer getClientResourceNecessityInformer() {
		return this;
	}

	@Override
	public IEconomicAgent[] generateAntAgents(int count, IMarketResourceRedestributionSystem mrrs) {
		AntAgent[] result = new AntAgent[count];
		for (int i = 0 ; i < count; i++) {
			result[i] = new AntAgent(root, this);
			AntWalkStrategy antWalkStrategy = new AntWalkStrategy(
					root,
					this,
					result[i].getInputRS(),
					result[i].getOutputRS(),
					random,
					useBranchesEuristic
			);
			result[i].setWalkStrategy(antWalkStrategy);
			result[i].setRNInformer(antWalkStrategy, mrrs);
		}
		return result;
	}

	@Override
	public double computeFeramon(IOutcome outcome) {
		FeramonNote note = getNote(outcome);
		double feramon = note.computeFeramon();
		return feramon;
	}

	@Override
	public void storeFeramon(IOutcome outcome, double feramonValue) {
		FeramonNote fn = getNote(outcome);
		fn.storeFeramon(feramonValue);
	}

	public FeramonStorrage(
			INode root, 
			int feramonHistoryLength, 
			ResourceStorrage inputRS,
			ResourceStorrage outputRS,
			IRandom random
	) {
		this(root, feramonHistoryLength, inputRS, outputRS, random, true);
	}

	/**
	 * using strategy instead of <<boolean useBranchesEuristic>> is best design.
	 */
	public FeramonStorrage(
			INode root, 
			int feramonHistoryLength, 
			ResourceStorrage inputRS,
			ResourceStorrage outputRS,
			IRandom random, 
			boolean useBranchesEuristic
	) {
		this.feramonHistoryLength = feramonHistoryLength;
		this.random = random;
		this.useBranchesEuristic = useBranchesEuristic;
		this.root = root;
		this.state = root;
		this.inputRS = inputRS;
		this.outputRS = outputRS;
		
	}

	@Override
	public IOutcome doWalk(INode state) {
		if (state != this.state) { throw new RuntimeException("Unexpected."); }
		if (choosenOut == null) {
			chooseOutcome(state);
		}
		boolean enought = ResourceUtils.checkIfResourcesEnought(
				inputRS, 
				choosenOut
		);
		if (!enought) {
			return null;
		}
		ResourceUtils.processOut(inputRS, outputRS, choosenOut);
		IOutcome result = choosenOut;
		this.state = choosenOut.getDestination();
		choosenOut = null;
		return result;
	}

	// 4 listInfoItems
	private IOutcome choosenOut = null;
	private INode state;
	@Override
	public List<InfoItem> listInfoItems() {
		chooseOutcome(state);
		List<InfoItem> result = new ArrayList<InfoItem>();
		for (ResourceBasket rb : choosenOut.getRequirements()) {
			result.add(
					new InfoItem(rb, 0)
			);
		}
		return result;
	}

	private void chooseOutcome(INode state) {
		if (choosenOut != null) {
			return;
		}
		double bestFeramon = -Double.MAX_VALUE;
		IOutcome bestOut = null;
		for (IOutcome out : state.getOutcomes()) {
			double curFeramon = computeFeramon(out);
			if (bestFeramon < curFeramon) {
				bestFeramon = curFeramon;
				bestOut = out;
			}
		}
		choosenOut = bestOut;
	}

	private FeramonNote getNote(IOutcome outcome) {
		if (!outcome2note.containsKey(outcome)) {
			FeramonNote note = new FeramonNote(feramonHistoryLength);
			outcome2note.put(outcome, note);
			return note;
		}
		return outcome2note.get(outcome);
	}
}
